package com.arlin.spring.factory;

import com.arlin.spring.annotation.Scope;
import com.arlin.spring.annotation.ioc.Component;
import com.arlin.spring.annotation.ioc.Controller;
import com.arlin.spring.annotation.ioc.Repository;
import com.arlin.spring.annotation.ioc.Service;
import com.arlin.spring.common.StringUtil;
import com.arlin.spring.context.ApplicationContext;
import com.arlin.spring.context.BeanDefinition;
import com.arlin.spring.context.SpringContext;
import com.arlin.spring.core.aop.factory.AopProxyBeanPostProcessorFactory;
import com.arlin.spring.core.aop.processor.BeanPostProcessor;
import com.arlin.spring.core.config.ConfigurationFactory;
import com.arlin.spring.core.config.ConfigurationManager;
import com.arlin.spring.enums.ScopeValue;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @ClassName: DefaultBeanFactory
 * @Description: 默认Bean工厂
 * @Author: arlin
 * @Date: 2021/12/11
 */
public class DefaultBeanFactory {

    public static void loadBeans(SpringContext context) {
        initContext(context);
    }

    private static void initContext(SpringContext context) {
        for (Class<?> clazz : ClassFactory.CLASSES.get(Component.class)) {
            String beanName = getBeanName(clazz);
            Object bean = newBeanInstance(clazz);
            context.setBean(beanName, bean);
            BeanDefinition beanDefinition = new BeanDefinition();
            beanDefinition.setBeanClass(clazz);
            if (clazz.isAnnotationPresent(Scope.class)) {
                Scope scope = clazz.getAnnotation(Scope.class);
                ScopeValue value = scope.value();
                beanDefinition.setScope(value);
            }
            context.setDefinition(beanName, beanDefinition);
        }
        // 创建读取和管理resources资源文件的Bean
        String configBeanName = ConfigurationManager.class.getSimpleName();
        context.getIOCContext().put(configBeanName, new ConfigurationManager(ConfigurationFactory.getDefaultConfiguration()));
        BeanDefinition beanDefinition = new BeanDefinition(ScopeValue.SINGLETON, ConfigurationManager.class);
        context.setDefinition(configBeanName, beanDefinition);
    }

    /**
     * 获取bean的名字
     *
     * @param clazz 类
     * @return 默认是参数clazz的全限定类名，如果有自定义就返回自定义的名字
     */
    public static String getBeanName(Class<?> clazz) {
        String beanName = clazz.getSimpleName();
        if (clazz.isAnnotationPresent(Component.class)) {
            Component component = clazz.getAnnotation(Component.class);
            beanName = component.value().equals("") ? clazz.getSimpleName() : component.value();
        } else if (clazz.isAnnotationPresent(Controller.class)) {
            Controller controller = clazz.getAnnotation(Controller.class);
            beanName = controller.value().equals("") ? clazz.getSimpleName() : controller.value();
        } else if (clazz.isAnnotationPresent(Repository.class)) {
            Repository repository = clazz.getAnnotation(Repository.class);
            beanName = repository.value().equals("") ? clazz.getSimpleName() : repository.value();
        } else if (clazz.isAnnotationPresent(Service.class)) {
            Service service = clazz.getAnnotation(Service.class);
            if (service.value().equals("")) {
                Class<?>[] interfaces = clazz.getInterfaces();
                if (interfaces.length != 0) {
                    beanName = interfaces[0].getSimpleName();
                } else {
                    beanName = clazz.getSimpleName();
                }
            } else {
                beanName = service.value();
            }
        }
        return StringUtil.upperCaseInitial(beanName);
    }

    /**
     * 创建bean实例
     *
     * @param clazz 类
     * @return 实例化bean
     */
    private static Object newBeanInstance(Class<?> clazz) {
        try {
            return clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new IllegalStateException(e.getMessage(), e);
        }
    }

    public static <T> T getBean(Class<T> clazz, SpringContext context) {
        String[] beanNames = getBeanNamesForType(clazz, (ApplicationContext) context);
        if (beanNames.length == 0) {
            throw new RuntimeException("not fount bean implement，the bean :" + clazz.getName());
        }
        String beanName = beanNames[0].substring(beanNames[0].lastIndexOf('.') + 1);
        BeanDefinition beanDefinition = context.getDefinition(beanName);
        ScopeValue scope = beanDefinition.getScope();
        Object beanInstance;
        if (ScopeValue.PROTOTYPE.equals(scope)) {
            beanInstance = newBeanInstance(beanDefinition.getBeanClass());
        } else {
            beanInstance = context.getIOCContext().get(beanName);
        }
        if (!clazz.isInstance(beanInstance)) {
            throw new RuntimeException("not fount bean implement，the bean :" + clazz.getName());
        }
        return clazz.cast(beanInstance);
    }

    private static String[] getBeanNamesForType(Class<?> type, ApplicationContext context) {
        String beanName = type.getName();
        String[] beanNames = context.getSINGLE_BEAN_NAMES_TYPE_MAP().get(beanName);
        if (beanNames == null) {
            Set<Map.Entry<String, Object>> entries = context.getIOCContext().entrySet();
            beanNames = getBeanNames(type, entries);
            context.getSINGLE_BEAN_NAMES_TYPE_MAP().put(beanName, beanNames);
        }
        return beanNames;
    }

    public static String[] getBeanNames(Class<?> type, Set<Map.Entry<String, Object>> entries) {
        String[] beanNames;
        List<String> beanNamesList = new ArrayList<>();
        for (Map.Entry<String, Object> beanEntry : entries) {
            Class<?> beanClass = beanEntry.getValue().getClass();
            if (type.isInterface()) {
                Class<?>[] interfaces = beanClass.getInterfaces();
                for (Class<?> c : interfaces) {
                    if (type.getName().equals(c.getName())) {
                        beanNamesList.add(beanEntry.getKey());
                        break;
                    }
                }
            } else if (beanClass.isAssignableFrom(type)) {
                beanNamesList.add(beanEntry.getKey());
            }
        }
        beanNames = beanNamesList.toArray(new String[0]);
        return beanNames;
    }

    public static void applyBeanPostProcessors(SpringContext context) {
        Map<String, Object> IOCContext = context.getIOCContext();
        for (String beanName : IOCContext.keySet()) {
            Object beanInstance = IOCContext.get(beanName);
            BeanPostProcessor beanPostProcessor = AopProxyBeanPostProcessorFactory.get(beanInstance.getClass());
            beanInstance = beanPostProcessor.postProcessAfterInitialization(beanInstance);
            IOCContext.put(beanName,beanInstance);
        }
    }
}
